package com.kalvan.swagger.dubbo.config;

import com.kalvan.swagger.dubbo.reader.NameDiscover;
import io.swagger.annotations.ApiOperation;
import io.swagger.models.HttpMethod;
import io.swagger.util.ReflectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.config.ApplicationConfig;
import org.apache.dubbo.config.ReferenceConfig;
import org.apache.dubbo.config.spring.ServiceBean;
import org.apache.dubbo.config.spring.extension.SpringExtensionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;


/**
 * @author chenliang
 */
public class ReferenceManager {

    private static Logger logger = LoggerFactory.getLogger(ReferenceManager.class);
    /**
     * dubbo 配置
     */
    private static ApplicationConfig applicationConfig;
    /**
     * dubbo bean
     */
    private static Collection<ServiceBean> services = new HashSet<ServiceBean>();

    /**
     * 接口和实现类（代理）
     */
    private static Map<Class<?>, Object> interfaceRefMap = new ConcurrentHashMap<Class<?>, Object>();

    static {
        //获取dubbo bean
        try {
            Field field = SpringExtensionFactory.class.getDeclaredField("CONTEXTS");
            field.setAccessible(true);
            Set<ApplicationContext> contexts = (Set<ApplicationContext>) field.get(new SpringExtensionFactory());
            for (ApplicationContext context : contexts) {
                services.addAll(context.getBeansOfType(ServiceBean.class).values());
            }
        } catch (Exception e) {
            logger.error("Get All Dubbo Service Error", e);
        }
        //获取接口和实现类map
        for (ServiceBean<?> bean : services) {
            interfaceRefMap.putIfAbsent(bean.getInterfaceClass(), bean.getRef());
        }
        //获取application
        if (!services.isEmpty()) {
            ServiceBean<?> bean = services.toArray(new ServiceBean[]{})[0];
            applicationConfig = bean.getApplication();
        }

    }

    /**
     * 获取dubbo配置
     *
     * @return
     */
    public static ApplicationConfig getApplication() {
        return applicationConfig;
    }

    /**
     * 获取所有dubbo接口和服务
     *
     * @return
     */
    public static Map<Class<?>, Object> getInterfaceRefMap() {
        return interfaceRefMap;
    }

    /**
     * 获取一个接口和接口实现
     *
     * @param interfaceClass
     * @return
     */
    public static Entry<Class<?>, Object> getInterfaceRef(String interfaceClass) {
        Set<Entry<Class<?>, Object>> entrySet = interfaceRefMap.entrySet();
        for (Entry<Class<?>, Object> entry : entrySet) {
            if (entry.getKey().getName().equals(interfaceClass)) {
                return entry;
            }
        }
        return null;
    }


    /**
     * 根据参数匹配具体的方法
     *
     * @param interfaceClass
     * @param methodName
     * @param operationId
     * @param requestMethod
     * @param paramNames
     * @return
     */
    public static Method matchRefMethod(
            String interfaceClass, String methodName, String operationId, String requestMethod,
            Set<String> paramNames) {
        Method[] refMethods = findRefMethods(interfaceClass, methodName, operationId, requestMethod);
        if (refMethods.length == 0) {
            return null;
        }
        if (refMethods.length == 1) {
            return refMethods[0];
        }
        List<RateMethod> rateMethods = new ArrayList<RateMethod>();
        for (Method method : refMethods) {
            String[] parameterNames = NameDiscover.getParameterNames(method);
            if (parameterNames == null) {
                return method;
            }
            float correctRate = 0.0f;
            int hit = 0;
            int error = 0;
            for (String paramName : parameterNames) {
                if (paramNames.contains(paramName)) {
                    hit++;
                } else {
                    error++;
                }
            }
            correctRate = error / (float) hit;
            rateMethods.add(new RateMethod(method, (int) correctRate * 100));
        }
        if (rateMethods.isEmpty()) {
            return null;
        }
        Collections.sort(rateMethods, new Comparator<RateMethod>() {
            @Override
            public int compare(RateMethod o1, RateMethod o2) {
                return o2.getRate() - o1.getRate();
            }
        });
        return rateMethods.get(0).getMethod();
    }

    /**
     * 获取dubbo服务代理类
     *
     * @param interfaceClass
     * @return
     */
    public static Object getProxy(String interfaceClass) {
        for (ServiceBean<?> service : services) {
            if (interfaceClass.equals(service.getInterfaceClass().getName())) {
                ReferenceConfig<Object> reference = new ReferenceConfig<Object>();
                reference.setApplication(service.getApplication());
                reference.setRegistry(service.getRegistry());
                reference.setRegistries(service.getRegistries());
                reference.setInterface(service.getInterfaceClass());
                reference.setVersion(service.getVersion());
                return reference.get();
            }
        }
        return null;
    }

    /**
     * 根据方法名找到实现类方法
     *
     * @param methodName
     * @return
     */
    private static Method[] findRefMethods(String interfaceClass, String methodName, String operationId, String requestMethod) {
        Entry<Class<?>, Object> entry = getInterfaceRef(interfaceClass);
        if (entry == null) {
            logger.info("No Ref Service FOUND.");
            return null;
        }
        Method[] methods = entry.getKey().getMethods();
        List<Method> ret = new ArrayList<Method>();
        for (Method method : methods) {
            if (method.getName().equals(methodName)) {
                Method m;
                try {
                    m = entry.getValue().getClass().getMethod(method.getName(), method.getParameterTypes());
                    final ApiOperation apiOperation = ReflectionUtils.getAnnotation(m, ApiOperation.class);
                    String nickname = null == apiOperation ? null : apiOperation.nickname();
                    if (operationId != null) {
                        if (!operationId.equals(nickname)) {
                            continue;
                        }
                    } else {
                        if (StringUtils.isNotBlank(nickname)) {
                            continue;
                        }
                    }
                    if (requestMethod != null) {
                        String httpMethod = null == apiOperation ? null : apiOperation.httpMethod();
                        if (StringUtils.isNotBlank(httpMethod) && !requestMethod.equals(httpMethod)) {
                            continue;
                        }
                        if (StringUtils.isBlank(httpMethod) && !requestMethod.equalsIgnoreCase(HttpMethod.POST.name())) {
                            continue;
                        }
                    }
                    ret.add(m);
                } catch (NoSuchMethodException e) {
                    logger.error("NoSuchMethodException", e);
                } catch (SecurityException e) {
                    logger.error("SecurityException", e);
                }
            }
        }
        return ret.toArray(new Method[]{});
    }
}
